<?php
////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2011 Alexandre Vaillancourt                                  //
//                                                                            //
// Permission is hereby granted, free of charge, to any person obtaining a    //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation  //
// the rights to use, copy, modify, merge, publish, distribute, sublicense,   //
// and/or sell copies of the Software, and to permit persons to whom the      //
// Software is furnished to do so, subject to the following conditions:       //
//                                                                            //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software.                        //
//                                                                            //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        //
// DEALINGS IN THE SOFTWARE.                                                  //
////////////////////////////////////////////////////////////////////////////////

/**
 * Base of Rappelz-hunbot
 * 
 * @author Alexandre Vaillancourt <alexandre.vaillancourt@gmail.com>
 * @license http://code.google.com/p/rappelz-hunbot/wiki/License MIT License
 * @copyright Copyright (c) 2011 Alexandre Vaillancourt
 * @since v.0.1.0
 * @package rappelz-hunbot
 */

require_once('include.db_objects.php');
require_once('wikibot.classes.php');
require_once('data.php');
require_once('config.php');
require_once('class.Log.php');
require_once('class.extra_functions.php');
require_once('Mail.php');


/**
 * How many secondes between requests to the server
 */
define('SLEEP_TIME', 1);

/**
 * class Rappelz_hunbot
 * Base class of Rappelz-hunbot
 * 
 * @package rappelz-hunbot
 */
class Rappelz_hunbot {

	/**
	 * function run
	 * Entry point of the bot.
	 * @param string $schedule what to do: 'daily', 'weekly'
	 * @param string $user the name of the user asking the request
	 * @param string $pass the password of the user asking the request
	 * @return int 0 -> All right, 1 -> Authentification failed, 2 -> Couldn't send log via email
	 */
	function run($schedule, $user, $pass) {
		
		if($user != USERNAME || sha1($pass) != USERPASS)
		{
			Log::get_instance()->log(Log::LEVEL_FATAL, 'auth:fail');
			return 1;
		}
		
		if($schedule == 'daily') {
			// Job to execute 
			$before = time();
			Log::get_instance()->log(Log::LEVEL_INFO, 'run:daily:start');
			$this->execute_update_grand_list(true);
			$this->execute_update_quest_chains(false);
			$this->execute_update_sta_potion_list(false);
			Log::get_instance()->log(Log::LEVEL_INFO, 'run:daily:end');
			$after = time();
			
			if($this->send_log($before, $after)) {
				return 0;
			}
			else {
				return 2;
			}
			
		}
		else if($schedule == 'weekly') {
			Log::get_instance()->log(Log::LEVEL_INFO, 'run:weekly:start');

	// $site_stats = get_site_stats();
	
	// $intro = <<< EOF
// Since this wiki is dedicated to US/english servers of Rappelz, here is a list to help non-US/non-english Rappelz players find a Wikia site they would prefer. (A wiki in their own language, for instance.)

// If you know a Rappelz wiki not listed here, please add it to the [[Rappelz_Wiki_talk:Other Rappelz Wikis|talk]] page as this list semi-automatically generated. If you feel this list is really out of date, please ask for an update on the talk page or on the [[Rappelz_Wiki_talk:Other Rappelz Wikis|talk]] page of ~~~. 


// EOF;
	
	// $table = format_site_stats_as_wiki_table($site_stats);
	// $print_content = $intro . $table;


			Log::get_instance()->log(Log::LEVEL_INFO, 'run:weekly:end');
		}
		
	}
	
	/**
	 * function execute_update_grand_list
	 * Executes the update of The Grand List of Quests
	 * @param string $should_fetch_data bool true if data has to be fetched first form wikia
	 */
	private function execute_update_grand_list($should_fetch_data) {
		Log::get_instance()->log(Log::LEVEL_INFO, 'execute_update_grand_list:start');
		if($should_fetch_data) {
			$this->fetch_quest_data_from_wikia_into_db();
		}

		$quests = new Questpage();
		$quests = $quests->GetList(array(array("questpageId", ">", "0")), "levelvalue asc, name");
		
		$page_content = GRAND_LIST_PAGE_HEADER . $this->get_grand_list_table($quests);
		
		//$this->update_article_if_diff('The Grand List of Quests', $page_content, 'Bot: Updating with latest data');
		$this->update_article_if_diff('User:R.hunbot/The Grand List of Quests', $page_content, 'Bot: Updating with latest data');
		Log::get_instance()->log(Log::LEVEL_INFO, 'execute_update_grand_list:end');
	}
	
	/**
	 * function execute_update_quest_chains
	 * Executes the update of the quest chains
	 * @param string $should_fetch_data bool true if data has to be fetched first form wikia
	 */
	private function execute_update_quest_chains($should_fetch_data) {
		if($should_fetch_data) {
			$this->fetch_quest_data_from_wikia_into_db();
		}
		
	}
	
	/**
	 * function execute_update_sta_potion_list
	 * Executes the update of the STA potion list
	 * @param string $should_fetch_data bool true if data has to be fetched first form wikia
	 */
	private function execute_update_sta_potion_list($should_fetch_data) {
		if($should_fetch_data) {
			$this->fetch_quest_data_from_wikia_into_db();
		}
		
	}
	
	/**
	 * function update_article_if_diff
	 * Updates an article if its content is diffenrent than what's in in_content
	 * @param string $in_article string article to update
	 * @param string $in_content string content of the update
	 * @param string $in_summary string summary of the update
	 */
	private function update_article_if_diff($in_article, $in_content, $in_summary) {
		$wpapi = new wikipediaapi();

		$retreived_dat = $wpapi->revisions($in_article, 1, 'older', true);
		
		$content = $retreived_dat[0];

		// Make consistent line endings
		$new_content = str_replace("\r\n", "\n", $content['*']);
		$in_content = str_replace("\r\n", "\n", $in_content);
		
		if($new_content != $in_content) {
			// New content
		
			// Login
			if(!$wpapi->login(WIKI_USERNAME, WIKI_USERPASS)) {
				Log::get_instance()->log(Log::LEVEL_FATAL, 'wiki:login:fail:'.WIKI_USERNAME.":$in_article");
				return;
			}
			
			if($wpapi->edit($in_article, $in_content, $in_summary, true)) {
				// Update succeeded
				Log::get_instance()->log(Log::LEVEL_INFO, "edit:[[$in_article]]:success:".$retreived_dat[0]['revid']);
				$retreived_dat = $wpapi->revisions($in_article, 1, 'older', false);

				// Update local DB
				$last_update = new contentupdate();
				$last_update->page = $in_article;
				$last_update->time = time();
				$last_update->content = $in_content;
				$last_update->revision = $retreived_dat[0]['revid'];
				$last_update->SaveNew();
				// if(!empty($last_update->Save())) {
					// Log::get_instance()->log(Log::LEVEL_ERROR, "savecontentupdate:[[$in_article]]::fail");
				// }
			}
			else {
				// Update failed
				Log::get_instance()->log(Log::LEVEL_ERROR, "edit:[[$in_article]]:fail");
			}
		}
		else {
			Log::get_instance()->log(Log::LEVEL_INFO, "update:[[$in_article]]:notneeded");
		}
		
	}

	/**
	 * function fetch_quest_data_from_wikia_into_db
	 * Flush quest data from DB and updates it with new quest data
	 */
	private function fetch_quest_data_from_wikia_into_db() {
		
		// Flush content
		$questpage_obj = new Questpage();
		$questpage_obj->DeleteList(array(array("questpageid", ">=", "0")));

		// Get quest names
		
		$quest_list = $this->get_quest_list("Quests");
		//$quest_list = $this->get_quest_list("Quests by NPC - A Strange Girl");
		
		// Get the quests
		foreach($quest_list as $quest_name) {
			// Respect!
			sleep(SLEEP_TIME);
			
			$questpage_obj = $this->fetch_quest_from_wikia($quest_name);
			
			if($questpage_obj != null) {
				$this->fill_formated_ints($questpage_obj);
				if($questpage_obj->Save()) {
					Log::get_instance()->log(Log::LEVEL_DEBUG, "retrievequest:success:$quest_name");
				}
				else {
					Log::get_instance()->log(Log::LEVEL_ERROR, "retrievequest:fail:$quest_name");
				}
			}
		}
	}
	
	/**
	 * function fetch_quest_from_wikia
	 * Retreieves a single quest from wikia
	 * @param string $in_quest_name name of the quest to retreive
	 * @return Questpage quest page object containing the content of the page, or null if not a questpage
	 */
	private function fetch_quest_from_wikia($in_quest_name) {

		$wpapi = new wikipediaapi();

		$retreived_dat = $wpapi->revisions($in_quest_name, 1, 'older', true);
		
		$content = $retreived_dat[0];
		
		$template_content = extra_functions::extract_template($content['*'], 'Questpage');

		if( !$template_content ) {
			return null;
		}

		$new_questpageobj = new Questpage();
		$new_questpageobj->name = $in_quest_name;
		$new_questpageobj->revid = $content['revid'];
		$new_questpageobj->content = $template_content;
		
		$template_elements = extra_functions::extract_parameters($template_content);
		$template_elements = array_change_key_case($template_elements, CASE_UPPER);

		if(isset($template_elements['LEVEL'])) $new_questpageobj->level = $template_elements['LEVEL'][0]; // $level='', 
		if(isset($template_elements['RACE'])) $new_questpageobj->race = $template_elements['RACE'][0]; // $race='', 
		if(isset($template_elements['NPC'])) $new_questpageobj->npc = $template_elements['NPC'][0]; // $npc='', 
		if(isset($template_elements['ISLAND'])) $new_questpageobj->island = $template_elements['ISLAND'][0]; // $island='', 
		if(isset($template_elements['TOWN'])) $new_questpageobj->town = $template_elements['TOWN'][0]; // $town='', 
		if(isset($template_elements['AREA'])) $new_questpageobj->area = $template_elements['AREA'][0]; // $area='', 
		if(isset($template_elements['FIELD'])) $new_questpageobj->field = $template_elements['FIELD'][0]; // $field='', 
		if(isset($template_elements['DUNG'])) $new_questpageobj->dung = $template_elements['DUNG'][0]; // $dung='', 
		if(isset($template_elements['OBJ'])) $new_questpageobj->obj = $template_elements['OBJ'][0]; // $obj='', 
		if(isset($template_elements['EXP'])) $new_questpageobj->exp = $template_elements['EXP'][0]; // $exp='', 
		if(isset($template_elements['JP'])) $new_questpageobj->jp = $template_elements['JP'][0]; // $jp='', 
		if(isset($template_elements['R'])) $new_questpageobj->r = $template_elements['R'][0]; // $r='', 
		if(isset($template_elements['P'])) $new_questpageobj->p = $template_elements['P'][0]; // $p='', 
		if(isset($template_elements['BASIC'])) $new_questpageobj->basic = $template_elements['BASIC'][0]; // $basic='', 
		if(isset($template_elements['CHOICE'])) $new_questpageobj->choice = $template_elements['CHOICE'][0]; // $choice='', 
		if(isset($template_elements['BONUS'])) $new_questpageobj->bonus = $template_elements['BONUS'][0]; // $bonus='', 
		if(isset($template_elements['PREV'])) $new_questpageobj->prev = $template_elements['PREV'][0]; // $prev='', 
		if(isset($template_elements['NEXT'])) $new_questpageobj->next = $template_elements['NEXT'][0]; // $next='', 
		if(isset($template_elements['INTRO'])) $new_questpageobj->intro = $template_elements['INTRO'][0]; // $intro='', 
		if(isset($template_elements['OBJECT'])) $new_questpageobj->object = $template_elements['OBJECT'][0]; // $object='', 
		if(isset($template_elements['WALK'])) $new_questpageobj->walk = $template_elements['WALK'][0]; // $walk='', 
		if(isset($template_elements['CON'])) $new_questpageobj->con = $template_elements['CON'][0]; // $con='', 
		if(isset($template_elements['MISC'])) $new_questpageobj->misc = $template_elements['MISC'][0]; // $misc='', 

		return $new_questpageobj;
	}

	/**
	 * function try_to_int
	 * Convert a string to it's most probable int value, or 0
	 * @param string $in_val string value in which to find an int
	 * @return int value interpreted from passed parameter or 0
	 */
	private function try_to_int($in_val) {
		$in_val = trim($in_val);
		$in_val = str_replace(',', '', $in_val);
		
		if(preg_match("/([0-9]+)/", $in_val, $match))
			return $match[1];
		
		return 0;
	}

	/**
	 * function fill_formated_ints
	 * Try to fetch int values from the quest object and update the quest object
	 * @param Questpage $inout_questpageobj (passed by reference) object to modify
	 */
	private function fill_formated_ints(&$inout_questpageobj)
	{
		$inout_questpageobj->levelvalue = $this->try_to_int($inout_questpageobj->level); // $levelvalue='', 
		$inout_questpageobj->expvalue = $this->try_to_int($inout_questpageobj->exp); // $expvalue='', 
		$inout_questpageobj->jpvalue = $this->try_to_int($inout_questpageobj->jp); // $jpvalue='', 
		$inout_questpageobj->rvalue = $this->try_to_int($inout_questpageobj->r); // $rvalue='', 
		$inout_questpageobj->pvalue = $this->try_to_int($inout_questpageobj->p); // $pvalue='', 
	}
	
	/**
	 * function get_quest_list
	 * Get all the quests in the category and sub categories.
	 * @param string $in_category name of the category without the Category: namespace
	 * @return array(string) list of quests in the specified category
	 */
	private function get_quest_list($in_category) {
		
		$wpapi = new wikipediaapi();

		$categories_todo = array($in_category => $in_category);
		$categories_done = array();
		$quests = array();
		
		while(count($categories_todo) > 0) {
			$keys = array_keys($categories_todo);
			$category_name = $keys[0];

			Log::get_instance()->log(Log::LEVEL_DEBUG, "categorymembers:$category_name");
			// Ask the site for members
			$fetched_category_members = $wpapi->categorymembers($category_name);
			
			// Don't hammer the server
			sleep(SLEEP_TIME);
			
			// Check every member
			foreach($fetched_category_members as $member) {
				// Check if it's a category
				if(preg_match('/^Category:(.*)$/', $member['title'], $match)) {
					$name = $match[1];
					// If so, add it to the list to scan if not done already
					if(!isset($categories_done[$name])
						&& !isset($categories_todo[$name])) {
						$categories_todo[$name] = $name;
					}
				}
				else {
					// Assume it's a quest page, make sure it's there only once
					Log::get_instance()->log(Log::LEVEL_DEBUG, 'questfound:'.$member['title']);
					$quests[$member['title']] = $member['title'];
				}
			}
			
			// Mark category as treated
			$categories_done[$category_name] = $category_name;
			
			// Remove from array
			unset($categories_todo[$category_name]);
		}
		
		return $quests;	
	}

	/**
	 * function get_grand_list_table
	 * Generates The Grand List of Quests tagle
	 * @param array(Questpage) $in_questpageobjets questpage objects from which to make the table
	 * @return string table content
	 */
	private function get_grand_list_table($in_questpageobjets) {

		$footer = '|}';
		$data = '';
		foreach($in_questpageobjets as $questpage_obj) {
			// make location
			$location = "";
			if(!empty($questpage_obj->island)) $location .= "'''Island''': <br />[[$questpage_obj->island]]";
			if(!empty($questpage_obj->town))
				if(!empty($location)) $location .= " <br />'''Town''': <br />[[$questpage_obj->town]]";
				else $location .= "'''Town''': <br />[[$questpage_obj->town]]";
			if(!empty($questpage_obj->area))
				if(!empty($location)) $location .= " <br />'''Area''': <br />[[$questpage_obj->area]]";
				else $location .= "'''Area''': <br />[[$questpage_obj->area]]";
			if(!empty($questpage_obj->field))
				if(!empty($location)) $location .= " <br />'''Field''': <br />[[$questpage_obj->field]]";
				else $location .= "'''Field''': <br />[[$questpage_obj->field]]";
			if(!empty($questpage_obj->dung))
				if(!empty($location)) $location .= " <br />'''Dungeon''': <br />[[$questpage_obj->dung]]";
				else $location .= "'''Dungeon''': <br />[[$questpage_obj->dung]]";
				
			// Make Reward
			$reward = '';
			if(!empty($questpage_obj->basic)) $reward .= "'''Basic''': <br />$questpage_obj->basic";
			if(!empty($questpage_obj->bonus))
				if(!empty($reward)) $reward .= " <br />'''Bonus''': <br />$questpage_obj->bonus";
				else $reward .= "'''Bonus''': <br />$questpage_obj->bonus";
			if(!empty($questpage_obj->choice))
				if(!empty($reward)) $reward .= " <br />'''Choice''': <br />$questpage_obj->choice";
				else $reward .= "'''Choice''': <br />$questpage_obj->choice";
			
			$data .= "|-\n";
			$data .= '| ' . ($questpage_obj->levelvalue == 0 ? "0\n": $questpage_obj->levelvalue . "\n");// ! Level
			$data .= "| [[$questpage_obj->name]]";// ! Name
			if(!empty($questpage_obj->race) && strtolower($questpage_obj->race) != "all") {
				$data .= " ($questpage_obj->race) ";
			}
			$data .= "\n";
			$data .= '| ' . (empty($questpage_obj->npc) ? "??\n" : "[[$questpage_obj->npc]]\n");// ! NPC
			$data .= '| ' . $location . "\n";// ! Location
			$data .= '| ' . (empty($questpage_obj->obj) ? "??\n" : "$questpage_obj->obj\n");// ! Objective
			$data .= '| ' . number_format($questpage_obj->expvalue) . "\n";// ! Exp
			$data .= '| ' . number_format($questpage_obj->jpvalue) . "\n";// ! JP
			$data .= '| ' . number_format($questpage_obj->rvalue) . "\n";// ! Rupees
			$data .= '| ' . number_format($questpage_obj->pvalue) . "\n";// ! Gen Points
			$data .= '| ' . $reward . "\n";// ! Rewards
		}
		
		return GRAND_LIST_TABLE_HEADER . $data . $footer;
	}
	
	/**
	 * function send_log
	 * Sends the content of the log DB to the email specified in configuration
	 * @param int $from timestamp before or equal to one of the first log entry
	 * @param int $to timestamp after or equal to one of the last log entry
	 * @return bool true if the mail have been sent
	 */
	private function send_log($from, $to) {
		$log_list = new logentry();
		$log_list = $log_list->GetList(array(array('time', '>=', $from), array('time', '<=', $to), array('level', '>=', Log::LEVEL_INFO)), 'time');
		
		$text = "<table>\n";
		foreach($log_list as $mylog) {
			$text .= '<tr><td>' . date('Y-m-j H:i:s', $mylog->time) . '</td><td>' . $mylog->level . '</td><td>' . $mylog->entry . "</td></tr>\n";
		}
		$text .= "</table>\n";

		$mail_sender = Mail::factory('smtp', array(
			'host' => SMTP_HOST,
			'port' => SMTP_PORT,
			'auth' => true,
			'username' => SMTP_USERNAME,
			'password' => SMTP_PASSWORD
			));
		
		$headers['From']    = SMTP_FROM;
		$headers['To']      = STMP_TO;
		$headers['Subject'] = 'Rappelz_hunbot update log';
		$headers['Content-type'] = 'text/html';

		return $mail_sender->send($headers['To'], $headers, $text);
	}
}

?>